home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Resources / Chat & Communication / Digsby build 37 / digsby_setup.exe / lib / common / notifications.pyo (.txt) < prev    next >
Python Compiled Bytecode  |  2008-10-13  |  15KB  |  480 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyo (Python 2.5)
  3.  
  4. from __future__ import with_statement
  5. import wx
  6. import os.path as os
  7. from collections import defaultdict
  8. from util import traceguard, dictadd, dictsub, do, try_this, memoize
  9. import util.data_importer as importer
  10. from path import path
  11. from common.slotssavable import SlotsSavable
  12. from os.path import exists as path_exists
  13. from common import profile, pref
  14. from pprint import pformat, pprint
  15. from logging import getLogger
  16. log = getLogger('notifications')
  17. from wx import PyDeadObjectError
  18. PlaySound = wx.Sound.PlaySound
  19. from Queue import Queue, Empty
  20. from weakref import ref
  21.  
  22. class cancellables(Queue):
  23.     
  24.     def __init__(self):
  25.         Queue.__init__(self)
  26.  
  27.     
  28.     def cancel(self):
  29.         n = 0
  30.         
  31.         try:
  32.             while True:
  33.                 item = self.get(block = False)
  34.                 if isinstance(item, ref):
  35.                     item = item()
  36.                 
  37.                 if item is not None:
  38.                     
  39.                     try:
  40.                         wx.CallAfter(item.cancel)
  41.                     except PyDeadObjectError:
  42.                         pass
  43.                     except Exception:
  44.                         print_exc = print_exc
  45.                         import traceback
  46.                         print_exc()
  47.  
  48.                     n += 1
  49.                     continue
  50.         except Empty:
  51.             pass
  52.  
  53.         log.info('cancelled %d notification reactions', n)
  54.  
  55.  
  56.  
  57. def fire(topic, **info):
  58.     log.debug('fired topic %r', topic)
  59.     topics = _topics_from_string(topic)
  60.     types = set()
  61.     cancels = cancellables()
  62.     fired = False
  63.     
  64.     try:
  65.         notifications = profile.notifications
  66.     except AttributeError:
  67.         return log.warning('no notifications yet')
  68.  
  69.     always_show = info.get('always_show', [])
  70.     always_show = None if always_show is not None else set()
  71.     always_show.update(always_fire.get(topic, []))
  72.     if 'buddy' in info:
  73.         
  74.         try:
  75.             idstr = info['buddy'].idstr()
  76.             buddy_events = notifications.get(idstr, [])
  77.         except:
  78.             buddy_events = []
  79.  
  80.         log.debug('found %d buddy specific events', len(buddy_events))
  81.         if buddy_events:
  82.             fired = True
  83.             firetopics(topics, buddy_events, types = types, cancels = cancels, **info)
  84.         
  85.     
  86.     generic_events = notifications.get(None, [])
  87.     if generic_events:
  88.         fired = True
  89.         firetopics(topics, generic_events, types = types, cancels = cancels, **info)
  90.     
  91.     if always_show:
  92.         import gui.notificationview as nview
  93.         G = globals()
  94.         reactions = (set,)((lambda .0: for name in .0:
  95. G[name])(always_show))
  96.         ninfo = nview.get_notification_info()
  97.         for reaction in reactions - types:
  98.             args = dictadd(ninfo.get(topic, { }), info)
  99.             
  100.             def doit(cancels = cancels, reaction = reaction, args = args):
  101.                 cancellable = reaction()(**args)
  102.                 if hasattr(cancellable, 'cancel'):
  103.                     cancels.put(ref(cancellable))
  104.                 
  105.  
  106.             wx.CallAfter(doit)
  107.             fired = True
  108.         
  109.     
  110.     
  111.     try:
  112.         on_done = info['on_done']
  113.     except KeyError:
  114.         pass
  115.  
  116.     if not fired:
  117.         log.info('Calling on_done callback')
  118.         traceguard.__enter__()
  119.         
  120.         try:
  121.             on_done()
  122.         finally:
  123.             pass
  124.  
  125.     
  126.     return cancels
  127.  
  128.  
  129. def firetopics(topics, events, types, cancels, **info):
  130.     import gui.notificationview as nview
  131.     if types is None:
  132.         types = set()
  133.     
  134.     ninfo = nview.get_notification_info()
  135.     for topic in topics:
  136.         for event in events.get(topic, []):
  137.             (reaction, eventinfo) = getReaction(event, topic)
  138.             if reaction in types or reaction is None:
  139.                 continue
  140.             
  141.             template_info = ninfo.get(topic, { })
  142.             
  143.             def doit(cancels = cancels, reaction = reaction, args = dictadd(template_info, info), eventinfo = eventinfo):
  144.                 cancellable = reaction(**eventinfo)(**args)
  145.                 if hasattr(cancellable, 'cancel'):
  146.                     cancels.put(ref(cancellable))
  147.                 
  148.  
  149.             wx.CallAfter(doit)
  150.             types.add(reaction)
  151.         
  152.     
  153.     return types
  154.  
  155.  
  156. def getReaction(mapping, topic):
  157.     mapping = dict(mapping)
  158.     reaction = mapping.pop('reaction')
  159.     if isinstance(reaction, basestring):
  160.         reaction = globals().get(reaction, None)
  161.     
  162.     if reaction not in reactions_set:
  163.         return (None, None)
  164.     
  165.     if reaction is Sound:
  166.         active_soundset = active_soundset
  167.         SoundsetException = SoundsetException
  168.         import gui.notifications.sounds
  169.         
  170.         try:
  171.             soundset = active_soundset()
  172.         except SoundsetException:
  173.             soundset = { }
  174.  
  175.         
  176.         try:
  177.             sound_filename = soundset[topic]
  178.         except KeyError:
  179.             return (None, None)
  180.  
  181.         mapping.update(soundfile = sound_filename)
  182.     
  183.     return (reaction, mapping)
  184.  
  185.  
  186. class Reaction(object):
  187.     
  188.     def preview(self):
  189.         pass
  190.  
  191.     
  192.     def allowed(self):
  193.         cname = type(self).__name__.lower()
  194.         away = profile.status.away
  195.         if pref('notifications.enable_%s' % cname, True):
  196.             if away:
  197.                 pass
  198.         return bool(not pref('messaging.when_away.disable_%s' % cname, False))
  199.  
  200.     allowed = property(allowed)
  201.  
  202.  
  203. class Sound(Reaction):
  204.     desc = 'Play sound %(filename)s'
  205.     
  206.     def __init__(self, soundfile):
  207.         self.soundfile = soundfile
  208.  
  209.     
  210.     def __call__(self, **k):
  211.         if not self.allowed:
  212.             return None
  213.         
  214.         import gui.native.helpers as helpers
  215.         if pref('fullscreen.disable_sounds', False) and helpers.FullscreenApp():
  216.             return None
  217.         
  218.         if path_exists(self.soundfile):
  219.             PlaySound(self.soundfile)
  220.         
  221.  
  222.     
  223.     def preview(self):
  224.         self()
  225.  
  226.     
  227.     def __repr__(self):
  228.         return '<Sound %s>' % path(self.filename).basename()
  229.  
  230.  
  231.  
  232. class Alert(Reaction):
  233.     desc = 'Show alert "%(msg)s"'
  234.     
  235.     def __init__(self, msg):
  236.         self.msg = msg
  237.  
  238.     
  239.     def __call__(self, **info):
  240.         if not self.allowed:
  241.             return None
  242.         
  243.         wx.MessageBox(self.msg)
  244.  
  245.  
  246.  
  247. class Popup(Reaction):
  248.     
  249.     def desc(cls, info):
  250.         return 'Show a popup notification'
  251.  
  252.     desc = classmethod(desc)
  253.     
  254.     def __init__(self, sticky = False):
  255.         self.sticky = sticky
  256.  
  257.     
  258.     def __call__(self, **info):
  259.         import gui.native.helpers as helpers
  260.         if pref('fullscreen.disable_popups', True) and helpers.FullscreenApp():
  261.             return None
  262.         
  263.         if self.allowed or info.get('always_show', False):
  264.             popup = popup
  265.             import gui.popup
  266.             cpy = vars(self).copy()
  267.             cpy.update(info)
  268.             return popup(**cpy)
  269.         
  270.  
  271.  
  272.  
  273. class ShowContactList(Reaction):
  274.     DEFAULT_DURATION_SEC = 3
  275.     desc = 'Show the contact list for %(duration)s seconds'
  276.     
  277.     def __init__(self, duration):
  278.         self.duration = duration
  279.  
  280.     
  281.     def __call__(self):
  282.         print 'do buddylist stuff for', self.duration, 'sec'
  283.  
  284.  
  285.  
  286. class LogMessage(Reaction):
  287.     
  288.     def __init__(self, msg):
  289.         self.msg = msg
  290.  
  291.     
  292.     def __call__(self, **info):
  293.         if self.msg.find('%') != -1 and info.get('buddy', None) is not None:
  294.             log.info(self.msg, info['buddy'])
  295.         else:
  296.             log.info(self.msg)
  297.  
  298.  
  299.  
  300. class StartCommand(Reaction):
  301.     desc = 'Start external command "%(path)s"'
  302.     
  303.     def __init__(self, path):
  304.         self.path = path
  305.  
  306.     
  307.     def __call__(self, **info):
  308.         os.startfile(self.path)
  309.  
  310.     
  311.     def preview(self):
  312.         self()
  313.  
  314.  
  315.  
  316. def get_notification_info(_cache = []):
  317.     
  318.     try:
  319.         return _cache[0]
  320.     except:
  321.         pass
  322.  
  323.     skin = skin
  324.     import gui
  325.     mod = importer.yaml_import('notificationview', loadpath = [
  326.         skin.resourcedir()])
  327.     nots = _process_notifications_list(mod.__content__)
  328.     _cache.append(nots)
  329.     return nots
  330.  
  331.  
  332. def _process_notifications_list(nots_list):
  333.     odict_from_dictlist = odict_from_dictlist
  334.     import util
  335.     nots = odict_from_dictlist(nots_list)
  336.     ordered_underscores_to_dots(nots)
  337.     update_always_fire(nots)
  338.     return nots
  339.  
  340.  
  341. def add_notifications(nots_list):
  342.     mynots = _process_notifications_list(nots_list)
  343.     nots = get_notification_info()
  344.     for k in mynots:
  345.         nots[k] = mynots[k]
  346.     
  347.  
  348. always_fire = { }
  349.  
  350. def update_always_fire(nots):
  351.     always_fire.clear()
  352.     for name, info in nots.iteritems():
  353.         if 'always_show' in info:
  354.             reactions = info.get('always_show')
  355.             if isinstance(reactions, basestring):
  356.                 reactions = [
  357.                     reactions]
  358.             
  359.             always_fire[name] = reactions
  360.             continue
  361.     
  362.  
  363.  
  364. def ordered_underscores_to_dots(d):
  365.     ordered_keys = d._keys[:]
  366.     for i, key in enumerate(list(ordered_keys)):
  367.         if key and '_' in key:
  368.             new_key = key.replace('_', '.')
  369.             d[new_key] = d.pop(key)
  370.             ordered_keys[i] = new_key
  371.             continue
  372.     
  373.     d._keys = ordered_keys
  374.  
  375. from common.message import StatusUpdateMessage
  376.  
  377. class IMWinStatusUpdate(Reaction):
  378.     
  379.     def __init__(self, **info):
  380.         self.info = info
  381.  
  382.     
  383.     def __call__(self, **info):
  384.         self.info.update(info)
  385.         on_done = info.pop('on_done', (lambda : pass))
  386.         on_status = on_status
  387.         import gui.imwin.imhub
  388.         wx.CallAfter(on_status, StatusUpdateMessage(**self.info), on_done)
  389.  
  390.  
  391. reactions = [
  392.     Popup,
  393.     Alert,
  394.     Sound,
  395.     ShowContactList,
  396.     StartCommand,
  397.     IMWinStatusUpdate]
  398. if 'wxMac' in wx.PlatformInfo:
  399.     
  400.     class BounceDockIcon(Reaction):
  401.         
  402.         def __call__(self, **info):
  403.             wx.GetApp().MacRequestUserAttention(wx.NOTIFY_REPEAT)
  404.  
  405.  
  406.     reactions.extend([
  407.         BounceDockIcon])
  408.  
  409. reactions_set = set(reactions)
  410. default_notifications = {
  411.     None: {
  412.         'contact.returnsfromidle': [],
  413.         'email.new': [
  414.             {
  415.                 'reaction': 'Popup' }],
  416.         'error': [
  417.             {
  418.                 'reaction': 'Popup' }],
  419.         'facebook.alert': [
  420.             {
  421.                 'reaction': 'Popup' }],
  422.         'facebook.newsfeed': [
  423.             {
  424.                 'reaction': 'Popup' }],
  425.         'filetransfer.ends': [
  426.             {
  427.                 'reaction': 'Popup' }],
  428.         'filetransfer.error': [
  429.             {
  430.                 'reaction': 'Popup' }],
  431.         'filetransfer.request': [
  432.             {
  433.                 'reaction': 'Popup' }],
  434.         'message.received.background': [
  435.             {
  436.                 'reaction': 'Popup' }],
  437.         'message.received.initial': [
  438.             {
  439.                 'reaction': 'Sound' }],
  440.         'myspace.alert': [
  441.             {
  442.                 'reaction': 'Popup' }] } }
  443. for topic in [
  444.     'contact.signon',
  445.     'contact.signoff',
  446.     'contact.available',
  447.     'contact.away',
  448.     'contact.returnsfromidle',
  449.     'contact.idle']:
  450.     seq = default_notifications[None].setdefault(topic, [])
  451.     seq += [
  452.         {
  453.             'reaction': 'IMWinStatusUpdate' }]
  454.  
  455.  
  456. class Notification(SlotsSavable):
  457.     pass
  458.  
  459. TOPIC_SEP = '.'
  460.  
  461. def _topics_from_string(topic):
  462.     _check_topic_string(topic)
  463.     topiclist = topic.split(TOPIC_SEP)
  464.     topics = []([ TOPIC_SEP.join(topiclist[:x]) for x in xrange(1, len(topiclist) + 1) ])
  465.     return list(topics)
  466.  
  467. _topics_from_string = memoize(_topics_from_string)
  468.  
  469. def _check_topic_string(topic):
  470.     if not isinstance(topic, basestring):
  471.         raise TypeError('topic must be a string')
  472.     
  473.     if topic.find('..') != -1:
  474.         raise ValueError('consecutive periods not allowed in topic')
  475.     
  476.     if topic.startswith('.') or topic.endswith('.'):
  477.         raise ValueError('topic cannot start or end with a topic')
  478.     
  479.  
  480.